-- PropertyM
-- Create by panyl
-- 管理所有的可使用道具

module("PropertyM", package.seeall);

-- 所有的道具信息
local properties = {};

local rules = {};

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    -- 转换下item信息表
    local property_CSV = get_property_CSV();
    for i = 1, #property_CSV do
        properties[property_CSV[i].class_id] = property_CSV[i];
    end

    property_CSV = {};
end

-- 模块初始化
function init()
    if not _init then
        loadCsv();

        -- 载入所有的规则处理子模块
        rules = LOAD_PATH2("game/logic/module/property");
    end

    EventMgr.removeAll("PropertyM");
    EventMgr.register("PropertyM", event.DO_BONUS, function(bonus)
        -- 如果不是道具
        if bonus[1] ~= 1 or not isProperty(bonus[2]) then
            return;
        end

        -- 自动使用的道具
        local classId = bonus[2];
        local dbase = query(classId, "dbase") or {};
        if dbase["auto_use"] == 1 then
            go(classId);
        end
    end);
end

-- 检索
function query(classId, path)
    local m = properties[classId];

    if not m or not path then
        return m;
    end

    if not m[path] then
        return ItemM.query(classId, path);
    end

    return m[path];
end

-- 获取子模块
function getModule(ruleName)
    local rule = rules[ruleName];
    if rule == "TBL" then
        rules[ruleName] = LOAD_RUN("game/logic/module/property/" .. ruleName);
        return rules[ruleName];
    end

    return rule;
end

-- 判断是否可使用道具
function isProperty(classId)
    if not properties[classId] then
        return false;
    end

    return true;
end

-- 是否迷宫内的道具
function isDungeonProperty(classId)
    local targetType = query(classId, "target");
    if not targetType then
        return false;
    end

    return bit.band(targetType, PROPERTY_DUNGEON_RANGE) == targetType;
end

-- 获取所有道具
function getAllProperties()
    return table.keys(properties);
end

-- 获取玩家身上所有的道具
function getUserProperties()
    local m = {};
    local arr = getAllProperties();

    for _, classId in pairs(arr) do
        local amount = ItemM.getAmount(ME.user, classId);
        if amount > 0 then
            table.insert(m, classId);
        end
    end

    return m;
end

-- 获取所有某类型道具
function fetchPropertiesByType(type)
    local ret = {};
    for classId, info in pairs(properties) do
        if info.target == type then
            table.insert(ret, classId);
        end
    end

    return ret;
end

-- 判断是否需要延迟播放
function isDelayPlay(classId)
    -- 配置了需要延迟的
    local args = query(classId,  "args") or {};
    if args.delay then
        return true;
    end

    return false;
end

-- 道具起效
function apply(source, target, classId)
    -- 这里不做检查了
    DungeonLogM.addLog(string.format("%s对%s使用道具%s。",
        source:getName(), target:getName(), query(classId, "name")));

    local args = query(classId,  "args") or {};
    local dbase = query(classId, "dbase") or {};
    local eggs = query(classId,  "eggs") or {};
    local round = CombatM.getRound();
    local path;
    local prop;
    local pet;
    local keys = table.keys(args);

    table.sort(keys);
    for i = 1, #keys do
        local key = keys[i];
        local value = table.deepcopy(args[key]); -- 这里需要copy一份，免得后面修改了

        local mod = getModule(key);

        if mod then
            mod.apply(source, target, classId, value);
        end
    end

    return true;
end

-- 树屋道具起效
function treeHouseApply(source, target, classId, args)
    -- 这里不做检查了
    DungeonLogM.addLog(string.format("%s对%s使用道具%s。",
        source:getName(), target:getName(), ItemM.query(classId, "name")));

    local keys = table.keys(args);
    table.sort(keys);
    for i = 1, #keys do
        local key = keys[i];
        local value = table.deepcopy(args[key]); -- 这里需要copy一份，免得后面修改了

        local mod = getModule(key);

        if mod then
            mod.apply(source, target, classId, value);
        end
    end
    return true;
end

-- 能否触发彩蛋
function canGainEggs(classId)
    local eggs        = query(classId, "eggs");
    local triggerCond = query(classId, "eggs_trigger_cond");

    if #table.keys(eggs) <= 0 then
        return nil;
    end

    for key, value in pairs(triggerCond) do
        if key == "need_equip" then
            -- 需要装备指定宝物
            local equipArr = table.values(ME.user.equipments);
            local equipList = {};

            for _, v in pairs(equipArr) do
                if type(v[1]) == "number" then
                    table.insert(equipList, v[1]);
                end
            end

            for _, v in pairs(value) do
                if table.indexOf(equipList, v) == -1 then
                    -- 未装备
                    return nil;
                end
            end
            return eggs;
        end
    end
end

-- 判断能否执行奖励
function checkDoBonus(bonus)
    local function checkAttrib(attrib)
        local list = { "mp", "hp", "limit_hp", "limit_mp"};
        if attrib == "per_mp" then
            attrib = "mp";
        end
        if table.indexOf(list, attrib) ~= -1 then
            local ret = DungeonM.canRecoverAttrib(ME.user, attrib);
            if ret ~= true then
                if ret == "blood_poisoned" then
                    -- 中了血毒
                    return "failed_reason_blood_poisoned";
                elseif ret == "dark_ceremony" then
                    -- 暗咒
                    return "failed_reason_dark_ceremony";
                end
                return ret;
            end
        end
        return true;
    end

    if bonus[1] == 2 then
        return checkAttrib(bonus[2]);
    elseif type(bonus[1]) == "table" then
        for _, arr in pairs(bonus) do
            if arr[1] == 2 then
                local ret = checkAttrib(arr[2]);
                if ret ~= true then
                    return ret;
                end
            end
        end
    end

    return true;
end

-- 判断能否使用
function canUse(classId, pos)
    local info = properties[classId];

    -- 如果不是可使用道具
    if not info then
        trace("PropertyM", "物品%d不是可使用道具。", classId);
        return false;
    end

    -- 不能使用的类型
    if info.target == PROPERTY_SUBMIT then
        trace("PropertyM", "物品%d只能提交用。", classId);
        return false;
    end

    -- 不能使用
    if info["dbase"]["cant_use"] == 1 then
        trace("PropertyM", "物品%d配置了不可使用。", classId);
        return query(classId, "fail_tip");
    end

    -- 如果是召唤物
    if info.target == PROPERTY_SUMMON then
        local summonId = info["args"]["summon"];
        if not summonId then
            if info["args"]["villager_fighter"] then
                summonId = info["args"]["villager_fighter"][1];
            elseif info["args"]["multi_summon"] then
                summonId = info["args"]["multi_summon"][1];
            elseif info["args"]["kill_summon"] then
                summonId = info["args"]["kill_summon"]["summon"];
            elseif info["args"]["chess_summon"] then
                summonId = info["args"]["chess_summon"];
            elseif info["args"]["summon_id_formula"] then
                -- 配置了公式
                summonId = FormulaM.invoke(info["args"]["summon_id_formula"], ME.user, classId);
            end
        end
        local ret = SummonM.canDoSummon(summonId);
        if ret ~= true then
            trace("PropertyM", "当前不能召唤。");
            return ret;
        end
    end

    if info["target"] == PROPERTY_SELF and
       type(info["args"]["bonus"]) == "table" then
        -- 如果是将奖励类的道具
        local bonus = info["args"]["bonus"];
        if info["args"]["clear_status"] ~= 0  then
            local ret = checkDoBonus(bonus);
            if ret ~= true then
                return ret;
            end
        end
    end

    if type(info["args"]["elixir_bonus"]) == "table" then
        local ret = checkDoBonus(info["args"]["elixir_bonus"]);
        if ret ~= true then
            return ret;
        end
    end

    -- 任务类道具，需要完成任务才能使用
    local taskId = info["args"]["dungeon_task"];
    if type(taskId) == "number" and
       not DungeonTaskM.isCompleted(taskId) then
        return "task_property_use_failed";
    end

    -- 特殊召唤物
    if info["args"]["addition_summon"] ~= nil then
        local summonList = info["args"]["addition_summon"];
        for _, summon in pairs(summonList) do
            if type(summon) == 'number' then
                local ret = SummonM.canDoSummon(summon);
                if ret ~= true then
                    trace("PropertyM", "当前不能召唤。");
                    return ret;
                end
            end
        end
    end

    -- 塔罗牌召唤
    if info["args"]["tarot"] ~= nil then
        local tarotRule = info["args"]["tarot"];
        if tarotRule == "tower" then
            local tarotId = TarotM.getIdByRule(info["args"]["tarot"])
            local tarotArgs = TarotM.query(tarotId, "args");
            if type(tarotArgs) ~= "table" then
                trace("PropertyM", "数据有误。");
                return;
            end
            local summonId = tarotArgs["summon"];

            local ret = SummonM.canDoSummon(summonId);
            if ret ~= true then
                trace("PropertyM", "当前不能召唤。");
                return ret;
            end
        end
    end

    -- 召唤类物品，需要有空格才能召唤
    if info["dbase"]["summon_item"] == 1 then
        local canSummon = FormulaM.invoke("HAD_SUMMON_POS");

        if not canSummon then
            trace("PropertyM", "找不到有效的召唤位置");
            -- 特殊的召唤物品，优先用配置的提示
            local msg;
            if info["dbase"]["monster_summon"] == 1 then
                msg = query(classId, "fail_tip");
                if not msg or msg == "" then
                    msg = "do_summon_fail2";
                end
            else
                -- 通用的召唤失败提示
                msg = "do_summon_fail2";
            end
            return msg;
        end
    end

    -- 特殊层不能使用的物品
    if info["dbase"]["forbid_floor"] == 1 then
        -- 副本中不能使用
        if DungeonInstanceM.isInInstance() then
            return "forbid_floor";
        end

        -- boss层不能使用
        if DungeonBossM.isBossLayer(DungeonM.getDungeonId(), DungeonM.currentLayer()) then
            return "forbid_floor";
        end

        -- 夹层迷宫
        if UnrealDungeonM.isInUnreal() then
            return "forbid_floor";
        end
    end

    -- 必须在指定夹层使用
    if info["dbase"]["require_unreal"] then
        -- 副本中不能使用
        if UnrealDungeonM.getUnrealId() ~= info["dbase"]["require_unreal"] then
            return query(classId, "fail_tip");
        end
    end


    -- 如果玩家没有该物品
    if ItemM.getAmount(ME.user, classId) <= 0 then
        trace("PropertyM", "玩家%s没有道具%d。", ME.user:getName(), classId);
        return false;
    end

    -- 公式判断
    local ret = FormulaM.invoke("CAN_USE_PROPERTY", ME.user, classId);
    if true ~= ret then
        -- 如果有配置，优先用配置的提示
        local msg = query(classId, "fail_tip");
        if not msg and type(ret) == "string" then
            msg = string.format(getLocStr("cannot_use_property"), FieldsM.getFieldName(ret), query(classId, "name"));
        end

        doAlert(msg or "");

        return false;
    end

    -- 判断是否可以对召唤兽使用道具
    local result = FormulaM.invoke("CAN_USE_SUMMON_PROPERTY", ME.user, classId);
    if true ~= result then
        return result;
    end

     -- 判断能否在地牢中使用 携带的可以带进迷宫的道具
    local carriableItems = ItemM.getAllCarriableItems();
    if DungeonM.isInDungeon() and table.indexOf(carriableItems, classId) ~= -1 then

        local  dungeonItem = ME.user.dbase:query("dungeon_item", {});
        if dungeonItem[classId] == nil then
            dungeonItem[classId] = 0;
        end
        if dungeonItem[classId] <= 0 then
            return false;
        end
    end

    -- 如果有技能
    local skillInfo = info["args"]["skill"];
    if skillInfo then
        local ret = SkillM.canCast(ME.user, skillInfo["skill_id"], pos)
        if ret ~= true then
            if ret == "forbid_magic" then
                -- 处于禁魔结界
                ret = "forbid_magic_tip";
            elseif ret == "forbidden_magic" then
                -- 处于禁魔状态
                ret = "forbidden_magic_tip";
            elseif ret == "blood_poisoned" then
                -- 中了血毒
                ret = "failed_reason_blood_poisoned";
            elseif ret == "dark_ceremony" then
                -- 暗咒
                ret = "failed_reason_dark_ceremony";
            end

            trace("PropertyM", "玩家%s不能释放技能%d。", ME.user:getName(), skillInfo["skill_id"]);
            return ret;
        end
    end

    return true;
end

-- 使用道具
function use(classId, pos)
    local ret = go(classId, pos);
    if true ~= ret then
        return false;
    end

    local delay = isDelayPlay(classId);

    -- TODO: 判断是否需要同步，像”魔法扫帚“这样的道具就只能这样了，待优化
    local nextFloor = FormulaM.invoke("NEED_ENTER_NEXT_FLOOR", "use_property", classId);

    -- 一起使用的时候，走use_multi_property的流程
    DungeonM.addAction({ ["cmd"] = "use_property", ["pos"] = pos or 0, ["data"] = classId, });

    -- 如果格子上怪物已经死掉了，就去掉delay
    if pos and pos > 0 then
        local grid = DungeonM.getGridByPos(pos);
        local monster = grid.monster;

        if grid:isOpened() and monster and monster:isDead() then
            delay = false;
        end
    end

    -- 某些道具不需要消耗回合
    local dbase = query(classId, "dbase");

    -- 一起使用的时候，都用完才是一个回合
    if not nextFloor and not dbase["keep_round"] then
        if not delay then
            -- 如果无需延迟播放，直接抛出事件

            -- 一个回合事件
            EventMgr.fire(event.COMBAT_ROUND, pos);
        else
            -- 一个回合事件
            EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = pos, ["isDelay"] = delay, });
        end
    end

    -- TODO: 判断是否需要同步，像”魔法扫帚“这样的道具就只能这样了，待优化
    if nextFloor == "next_floor" then
        if not Socket.isConnected() then
            LoginM.tryReconnect(getLocStr("warning_title"), getLocStr("disconnect_tip"));
            LoginM.dungeonNextFloor(true);
            return true, true;
        end
        DungeonM.nextFloor(true);

        EventMgr.fire(event.WAIT_LOADING);
    elseif nextFloor == "back_floor" then
        if not Socket.isConnected() then
            LoginM.tryReconnect(getLocStr("warning_title"), getLocStr("disconnect_tip"));
            LoginM.needSyncFlag(true);
            return true, true;
        end

        DungeonM.sync();

        EventMgr.fire(event.WAIT_LOADING);
    end

    return true, true;
end

function go(classId, pos)
    -- 判断下
    local ret = canUse(classId, pos);
    if ret ~= true then
        return ret;
    end

    -- 查看目标是否正确
    local targetType = query(classId, "target") or 0;
    local target;
    if not pos or pos <= 0 then
        -- 没有指定目标，就是给自己用的
        if targetType ~= PROPERTY_SELF and
           targetType ~= PROPERTY_SUMMON then
            trace("PropertyM", "道具%d不能给自己使用。", classId);
            return false;
        end

        target = ME.user;
    else
        -- 道具不能对敌方使用
        if targetType ~= PROPERTY_ENEMY then
            trace("PropertyM", "道具%d不能对敌方使用。", classId);
            return false;
        end

        local grid = DungeonM.getGridByPos(pos);
        local monster = grid.monster;

        -- 目标不正确
        if  not grid:isOpened() or not grid:isMonster() or
            not monster or monster:isDead() then
            trace("PropertyM", "目标%d不是怪物或者目标已经死亡。", pos);
            return false;
        else
            target = monster;
        end
    end

    -- 先扣除物品
    local costCount = costProperty(ME.user, classId);

    -- 在地牢中使用 携带的可以带进迷宫的道具
    if DungeonM.isInDungeon() and ItemM.isCarriableItem(classId) and costCount > 0 then
        local dungeonItem = ME.user.dbase:query("dungeon_item", {});
        dungeonItem[classId] = dungeonItem[classId] - 1;
        ME.user.dbase:set("dungeon_item", dungeonItem);
   end

    -- 起效
    apply(ME.user, target, classId);

    -- 如果有统计需求
    local dbase = query(classId, "dbase");
    if dbase["use_stat"] then
        BuildingBonusM.addFieldStat(dbase["use_stat"], 1);
    end

    -- 抛出使用道具事件
    EventMgr.fire(event.USE_PROPERTY, { ["classId"] = classId, ["pos"] = pos,});

    return true;
end

-- 通用使用接口
function fastUse(classId, amount)
    -- 默认使用一个
    amount = amount or 1;

    -- 数量不足
    if ItemM.getAmount(ME.user, classId) < amount then
        return false;
    end

    -- 这里只能是在迷宫外使用的道具
    if DungeonM.getState() == DUNGEON_GAMING then
        return false;
    end

    -- 如果不是可使用道具
    if not isProperty(classId) then
        return false;
    end

    -- TODO: 暂不做其他判断


    -- 扣除道具
    ItemM.costAmount(ME.user, classId, amount);

    -- 作用amount次
    for i = 1, amount do
        apply(ME.user, ME.user, classId);
    end

    -- TODO: 发消息给服务器
    Communicate.send("CMD_FAST_USE_PROPERTY", {["class_id"] = classId, ["amount"] = amount, });
end

-- 扣除道具
function costProperty(user, classId)
    local dbase = query(classId, "dbase");
    local propertyUsed = user.dbase:query("property_used", {});

    -- 有次数限制(使用用数量是1时，使用一次,次数大于1时，应该是批量使用一个道具）
    -- 数量大于1时是批量使用的情况(如果表明使用多个，次数直接用完)
    if type(dbase["max_times"]) == "number"  then
        -- 次数+
        propertyUsed[classId] = (propertyUsed[classId] or 0) + 1;

        if propertyUsed[classId] < dbase["max_times"] then
            -- 还是有效的，更新使用次数就行了
            user.dbase:set("property_used", propertyUsed);
            return 0;
        end
    end

    -- 扣除
    ItemM.costAmount(user, classId, 1);

    -- 清除记录
    propertyUsed[classId] = nil;

    if dbase.cost_bonus and type(dbase.cost_bonus) == "table" then
        BonusM.doBonus(dbase.cost_bonus, "cost_bonus", false);
    end

    return 1;
end

-- 使用成功提示语
function getSuccessUseTip(classId)
    local tip = PropertyM.query(classId, "succ_tip");

    if type(tip) ~= "string" or #tip <= 0 then
        return "";
    end

    -- 如果是公式
    local formula;
    if string.startWith(tip, "formula_") then
        formula = string.sub(tip, string.len("formula_") + 1);
        return FormulaM.invoke(formula, classId);
    end

    return tip;
end

-- 道具伤害免疫修正
function fixPropertyDamage(source, target, classId, damage)
    -- 原始值
    local fixDamage = damage;

    -- 检查对象是否免疫道具伤害
    local prop = PropM.combine(target, "property_damage_immunity", 1);
    local rand = DungeonM.getRandSeed("property_damage_immunit") % 1000;
    if rand < prop[3] then
        -- 触发免疫
        fixDamage = 0;
    end

    -- 场上有魔镜
    if MagicMirrorM.getMagicMirror("property") >= 0
        and target.type ~= OBJECT_TYPE_USER then
        fixDamage = 0;
    end

    -- 概率免疫特定物品的伤害
    if PropertyM.query(classId, "target") == PROPERTY_SELF then
        prop = PropM.combine(source, "property_immunity", classId);

        if prop[3] > 0 then
            local args  = {["value"] = damage, ["class_id"] = classId,};
            fixDamage = PropM.trigger(source, prop[1], prop[2], args);
        end
    end

    -- 被削弱
    prop = PropM.combine(target, "property_damage_reduce", 1);
    fixDamage = PropM.apply(prop, fixDamage);

    return fixDamage;
end

-- 检查是否可以使用多个,（当前只支持对自己使用的情况）
function canUseMulti(classId)
    -- 检查下是不是可以使用的
    local isMulti = query(classId, "use_multi");
    if isMulti ~= 1 then
        return false;
    end

    -- 查看目标是否正确
    local targetType = query(classId, "target") or 0;
    local target;

    -- 只能是给自己用的
    if targetType ~= PROPERTY_SELF and
        targetType ~= PROPERTY_SUMMON then
        trace("PropertyM", "道具%d不是给自己使用，无法一起使用", classId);
        return false;
    end

    local amount = calcApplyTimes(classId);

    -- 当前数量不足,作用次数大于1才调用使用多个
    if amount < 2 then
        return false;
    end

    -- 如果单个不能用
    -- 则认为多个也不能用
    local ret = canUse(classId);
    if ret ~= true then
        return ret;
    end

    return true;
end

-- 使用多个道具，暂时只支持对自己使用
function useMulti(classId)
    -- 默认使用一个
    local ret = canUseMulti(classId);
    if ret ~= true then
        return ret, true;
    end

    local applyAmount = calcApplyTimes(classId);

    -- 作用amount次，作用次数需要另外算下
    for i = 1, applyAmount do
        go(classId);
    end
    local delay = isDelayPlay(classId);

    -- TODO: 判断是否需要同步，像”魔法扫帚“这样的道具就只能这样了，待优化
    local nextFloor = FormulaM.invoke("NEED_ENTER_NEXT_FLOOR", "use_property", classId);

    if not nextFloor then
        if not delay then
            -- 如果无需延迟播放，直接抛出事件

            -- 一个回合事件
            EventMgr.fire(event.COMBAT_ROUND, pos);
        else
            -- 一个回合事件
            EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = pos, ["isDelay"] = delay, });
        end
    end

    return true;
end

-- 批量使用的时候，计算生效次数所有次数
function calcApplyTimes(classId)
    -- 可以批量使用才去计算
    local amount;
    -- 当前数量是否有多个(迷宫试剂要另外处理)
    local carriableItems = ItemM.getAllCarriableItems();
    local  dungeonItem = ME.user.dbase:query("dungeon_item", {});
    if table.indexOf(carriableItems, classId) ~= -1 then
        amount = tonumber(dungeonItem[classId]);
    else
        amount = ItemM.getAmount(ME.user, classId);
    end

    local applyAmount = amount;
    local dbase = query(classId, "dbase");
    local propertyUsed = ME.user.dbase:query("property_used", {});

    if type(dbase["max_times"]) == "number" then
        local maxTimes = dbase["max_times"];
        local usedTimes = tonumber(propertyUsed[classId]);
        applyAmount = (amount - 1) * maxTimes + (maxTimes - usedTimes);
    end

    return applyAmount;
end

-- 判断是否显示使用多个情况(当前数量大于1，或者一个物品还可以使用多次)
function showUseMulti(classId)
    -- 只支持（对玩家起效的物品）多个一起使用
    local targetType = query(classId, "target") or 0;
    if targetType ~= PROPERTY_SELF and
        targetType ~= PROPERTY_SUMMON then
        return false;
    end

    -- 可不可以使用多个
    local is_multi = query(classId, "use_multi") or 0;

    -- 作用次数和使用数量可能不一致
    local applyAmount = calcApplyTimes(classId);

    -- 有的物品是一个但是可以使用多次
    if is_multi == 1 and applyAmount > 1 then
        return true;
    end

    return false;
end

-- 获取某类型道具集合
function getPropertyByTargetType(targetType)
    local ret = {};
    for classId, info in pairs(properties) do
        if info["target"] == targetType then
            table.insert(ret, classId);
        end
    end
    return ret;
end

-- 计算伤害类道具的最终伤害
function calcFinalDamage(source, target, classId, damage)
    -- 如果不是玩家，只考虑目标对象的伤害削减
    if source.type ~= OBJECT_TYPE_USER then
        return fixPropertyDamage(source, target, classId, damage);
    end

    local props;
    local addon = 0;
    local args  = {["value"] = damage, ["class_id"] = classId, ["target"] = target,};

    -- ------------- 计算source的prop影响下的总伤害 ------------------------
    -- ------------- 先计算总的加成，最后再相加 ----------------------------
    -- 概率造成额外的固定伤害
    props = PropM.fetchProps(source, "prob_fixed_damage");
    for _, prop in ipairs(props) do
        addon = addon + PropM.trigger(source, prop[1], prop[2], args);
    end

    -- 概率造成额外的百分比伤害
    props = PropM.fetchProps(source, "prob_per_damage");
    for _, prop in ipairs(props) do
        addon = addon + PropM.trigger(source, prop[1], prop[2], args);
    end

    -- 对有生命的敌人造成额外的百分比伤害
    props = PropM.fetchProps(source, "property_living_damage");
    for _, prop in ipairs(props) do
        addon = addon + PropM.trigger(source, prop[1], prop[2], args);
    end

    -- 对指定系别的对象造成额外的百分比伤害
    props = PropM.fetchProps(source, "property_style_damage");
    for _, prop in ipairs(props) do
        addon = addon + PropM.trigger(source, prop[1], prop[2], args);
    end

    damage = damage + addon;
    -- ---------------------------------------------------------
    addon = 0;

    -- 其他的道具伤害加成
    if PropertyM.query(classId, "target") ~= PROPERTY_SELF then
        prop = PropM.combine(source, "property_damage", 1);
        damage = PropM.apply(prop, damage);
    else
        -- 对自己使用，最多扣除生命到1点
        local sourceHp = source:queryAttrib("hp");
        damage = math.min(damage, sourceHp - 1);
    end

    -- 修正伤害值
    damage = fixPropertyDamage(source, target, classId, damage);

    return damage;
end

-- 计算道具产生的战斗结算奖励
function calcCombatStatBonus()
    local arr = getAllProperties();
    local ret = {};
    for _, classId in pairs(arr) do
        local amount = ItemM.getAmount(ME.user, classId);
        if amount > 0 then
            local args = query(classId, "args");
            local bonus = args["combat_stat_bonus"];
            if type(bonus) == "table" then
                if type(bonus[1]) == "number" then
                    -- 只有一种奖励
                    table.insert(ret, { bonus[1], bonus[2], amount * bonus[3] });
                else
                    -- 有多种奖励
                    for _, arr2 in ipairs(bonus) do
                        table.insert(ret, { arr2[1], arr2[2], amount * arr2[3] });
                    end
                end
            end
        end
    end
    return ret;
end
